﻿using System;
using System.Collections.Generic;
using System.Security.Policy;

using FireBus.Contracts;
using FireBus.Isolation;


namespace FireBus.Core
{
	public class IsolatedExtensionLoader : IExtensionLoader
	{
		private readonly Dictionary<ExtensionToken, AppDomain> _Domains =
			new Dictionary<ExtensionToken, AppDomain>();

		private readonly IMessageAccessor _MessageAccessor;

		private readonly IExtensionAccessor _ExtensionAccessor;


		public IsolatedExtensionLoader(
			IMessageAccessor messageAccessor,
			IExtensionAccessor extensionAccessor)
		{
			if (messageAccessor == null)
			{
				throw new ArgumentNullException("messageAccessor");
			}
			if (extensionAccessor == null)
			{
				throw new ArgumentNullException("extensionAccessor");
			}
			_MessageAccessor = messageAccessor;
			_ExtensionAccessor = extensionAccessor;
		}


		public ExtensionLoadResult Load(ExtensionToken token)
		{
			if (token == null)
			{
				throw new ArgumentNullException("token");
			}

			if (_Domains.ContainsKey(token))
			{
				throw new CannotLoadExtensionException(
					_MessageAccessor.ExtensionWithGivenNameWasLoadedAlready(token.Name));
			}
			var absolutePath = token.Path.GetAbsolutePath();

			var domainSetup =
				new AppDomainSetup
					{
						ApplicationBase = absolutePath,
						PrivateBinPath = absolutePath
					};

			var domain = AppDomain.CreateDomain(
				token.Name,
				new Evidence(AppDomain.CurrentDomain.Evidence),
				domainSetup);

			ExtensionAdapter extension;
			ExtensionMetadata metadata;
			try
			{
				var extensionControllerType = typeof (ExtensionController);
				var extensionController = 
					(ExtensionController)domain.CreateInstanceFromAndUnwrap(
						extensionControllerType.Assembly.ManifestModule.FullyQualifiedName,
						extensionControllerType.FullName);

				var extensionAccessorAdapter = new ExtensionAccessorAdapter(_ExtensionAccessor);
				
				extensionController.Initialize(
					token.Name, absolutePath, token.TypeName, extensionAccessorAdapter);
				extension = extensionController.GetInstance();
				metadata = extension.Metadata;
			}
			catch
			{
				try
				{
					AppDomain.Unload(domain);
				}
				catch(CannotUnloadAppDomainException)
				{
				}
				throw;
			}
			_Domains.Add(token, domain);
			var result = new ExtensionLoadResult(
				new Extension(extension, metadata), null);
			return result;
		}

		public Boolean CanUnload
		{
			get { return true; }
		}

		public void Unload(ExtensionToken token)
		{
			AppDomain domain;
			if (!_Domains.TryGetValue(token, out domain))
			{
				return;
			}
			_Domains.Remove(token);
			AppDomain.Unload(domain);
		}
	}
}
